package model

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"unicode/utf8"

	"github.com/invopop/jsonschema"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/bsontype"
)

type (
	ListOrDict struct {
		List []string
		Dict map[string]interface{}
	}
)

func (ListOrDict) JSONSchema() *jsonschema.Schema {
	return &jsonschema.Schema{
		OneOf: []*jsonschema.Schema{
			{
				Type:        "array",
				Items:       &jsonschema.Schema{Type: "string"},
				UniqueItems: true,
			},
			{
				Type: "object",
				PatternProperties: map[string]*jsonschema.Schema{
					".+": {OneOf: []*jsonschema.Schema{
						{Type: "string"},
						{Type: "boolean"},
						{Type: "number"},
					}},
				},
				AdditionalProperties: jsonschema.FalseSchema,
			},
		},
	}
}

func (l *ListOrDict) UnmarshalJSON(data []byte) error {
	if len(data) == 0 {
		return nil
	}

	first, size := utf8.DecodeRuneInString(string(data))
	if first == utf8.RuneError && size == 1 {
		return fmt.Errorf("cannot extract first rune from '%s'", string(data))
	}

	switch first {
	case '{':
		items := map[string]interface{}{}
		if err := json.Unmarshal(data, &items); err != nil {
			return fmt.Errorf("failed to unmarshall map: %w", err)
		}

		*l = ListOrDict{Dict: items}
	case '[':
		items := make([]string, 0)
		if err := json.Unmarshal(data, &items); err != nil {
			return fmt.Errorf("failed to unmarshall list: %w", err)
		}
		*l = ListOrDict{List: items}
	default:
		return fmt.Errorf("unsupported string '%s'", string(data))
	}

	return nil
}

func (l *ListOrDict) MarshalJSON() ([]byte, error) {
	if l == nil || len(l.List) == 0 || len(l.Dict) == 0 {
		return []byte{}, nil
	}

	if l.Dict != nil {
		return json.Marshal(l.Dict)
	}

	return json.Marshal(l.List)
}

func (l *ListOrDict) MarshalBSONValue() (bsontype.Type, []byte, error) {
	if l == nil || len(l.Dict) == 0 || len(l.List) == 0 {
		return bson.MarshalValue(bson.A{})
	}

	if l.Dict != nil {
		return bson.MarshalValue(l.Dict)
	}

	return bson.MarshalValue(l.List)
}

func (l *ListOrDict) UnmarshalBSONValue(t bsontype.Type, data []byte) error {
	raw := bson.RawValue{Type: t, Value: data}

	switch t {
	case bsontype.EmbeddedDocument:
		items := map[string]interface{}{}
		if err := raw.Unmarshal(&items); err != nil {
			return fmt.Errorf("failed to unmarshall embeded document: %w", err)
		}
		*l = ListOrDict{Dict: items}
	case bsontype.Array:
		items := make([]string, 0)
		if err := raw.Unmarshal(&items); err != nil {
			return fmt.Errorf("failed to unmarshall array: %w", err)
		}
		*l = ListOrDict{List: items}
	case bsontype.Null:
		*l = ListOrDict{}
	default:
		return fmt.Errorf("unsupported type '%s'", t)
	}

	return nil
}

func ConvertDictToDict(dict map[string]interface{}) map[string]string {
	result := map[string]string{}

	for key, value := range dict {
		var str string

		switch v := value.(type) {
		case nil:
			continue
		case string:
			str = v
		case int:
			str = strconv.FormatInt(int64(v), 10)
		case int32:
			str = strconv.FormatInt(int64(v), 10)
		case int64:
			str = strconv.FormatInt(v, 10)
		case float32:
			str = strconv.FormatFloat(float64(v), 'f', -1, 64)
		case float64:
			str = strconv.FormatFloat(v, 'f', -1, 64)
		case bool:
			str = strconv.FormatBool(v)
		default:
			fmt.Printf("type '%s' do not supported for converting dict to dict '%s':'%s'", v, key, value)
			continue
		}

		result[key] = str
	}

	return result
}

func ConvertListToDict(list []string, delimeter string) map[string]string {
	result := map[string]string{}

	for _, element := range list {
		split := strings.Split(element, environmentDelimiter)
		if len(split) != 2 {
			fmt.Printf("element '%s' does not contains delimeter '%s', skipping", element, delimeter)
			continue
		}

		result[split[0]] = split[1]
	}

	return result
}
